import logging
from typing import Optional
import time

from coin.exchange.base.order_enum import (get_str_proto_order_side)
from coin.exchange.kr_rest.product.product_impl import generate_product_from_str
from coin.exchange.kr_rest.feed.feed_impl import (
    generate_public_client,)
from coin.exchange.kr_rest.order.order_impl import (
    generate_private_client,)
from coin.proto.coin_query_pb2 import (ProductOrderElement, ProductPosition)
from xunkemgmt.support.accounting.logic.query_request import AccountRequest
from coin.support.accounting.logic.key_util import get_key_file

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)


class OrderClearError(RuntimeError):
  pass


class RestAccountManager(object):
  def __init__(self, account_request: Optional[AccountRequest] = None):
    key_file = get_key_file(account_request.market_type,
                            account_request.exchange,
                            account_request.owner,
                            account_request.api_version)
    self._account_request = account_request
    self._public_client = generate_public_client(account_request.market_type,
                                                 account_request.exchange,
                                                 account_request.api_version)
    self._private_client = generate_private_client(account_request.market_type,
                                                   account_request.exchange,
                                                   account_request.api_version,
                                                   key_file)

  @property
  def public_client(self):
    return self._public_client

  @property
  def private_client(self):
    return self._private_client

  def _get_product(self, symbol: str):
    product = generate_product_from_str(self._account_request.exchange,
                                        self._account_request.api_version,
                                        symbol)
    return product

  def ensure_working_orders_are_canceled(self, product, max_num_retries=10):
    # Clear all open orders.
    for _ in range(max_num_retries):
      if self.has_working_order(product):
        return
      else:
        try:
          self._private_client.query_cancel_product(product)
        except Exception as e:
          logger.error('query_cancel_product failed: %s', e)
          raise
        time.sleep(5)  # Is 5 seconds enough
    raise OrderClearError('Failed to clear all open orders!')

  def has_working_order(self, product):
    logger.debug('has_working_order: private_client.query_list_orders called.')
    try:
      update = self._private_client.query_list_orders(product)
    except Exception as e:
      logger.error('query_list_orders failed: %s', e)
      raise

    open_orders_bundle = update.msg
    return len(open_orders_bundle.each_order) == 0

  def is_order_open(self, order_element: ProductOrderElement):
    logger.debug('is_order_open: private_client.query_list_orders called.')
    product = self._get_product(order_element.symbol)
    try:
      update = self._private_client.query_list_orders(product)
    except Exception as e:
      logger.error('query_list_orders failed: %s', e)
      raise
    open_orders_bundle = update.msg
    return any(
        [order_element.order_id == order.order_id for order in open_orders_bundle.each_order])

  def is_order_filled(self, order_element: ProductOrderElement):
    logger.debug('is_order_filled: private_client.query_fills called.')
    product = self._get_product(order_element.symbol)
    try:
      update = self._private_client.query_fills(product)
    except Exception as e:
      logger.error('query_fills failed: %s', e)
      raise

    fill_bundle = update.msg
    return any([order_element.order_id == fill.order_id for fill in fill_bundle.each_fill])

  def submit_order(self, product, order_element: ProductOrderElement):
    order_side = get_str_proto_order_side(order_element.side)
    logger.debug('submit_order: private_client.query_submit called.')
    try:
      submit_update = self._private_client.query_submit(product,
                                                        order_side,
                                                        order_element.price,
                                                        order_element.qty)
    except Exception as e:
      logger.error('submit failed: %s', e)
      raise
    return submit_update

  def cancel_order(self, product, order_id):
    logger.debug('cancel_order: private_client.query_cancel called.')
    try:
      cancel_update = self._private_client.query_cancel(product, order_id)
    except Exception as e:
      logger.error('cancel %s failed %s', order_id, e)
      raise
    return cancel_update
